import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
import java.util.*;

public class Assembler
extends LMCElement {

    public static final String INVALID_LABEL =
	    "Illegal characters in label";
    public static final String DUPLICATE_LABEL =
	    "Duplicate label";
    public static final String UNKNOWN_MNEMONIC =
	    "Unknown mnemonic";
    public static final String UNEXPECTED_OPERAND =
	    "Unexpected operand";
    public static final String MISSING_OPERAND =
	    "Missing operand";
    public static final String INVALID_OPERAND =
	    "Illegal characters in operand";
    public static final String UNKNOWN_LABEL =
	    "Label in operand not defined";
    public static final String ADDRESS_TOO_LARGE =
	    "Operand address too large";
    public static final String ADDRESS_TOO_SMALL =
	    "Operand address too small";
    public static final String VALUE_TOO_LARGE =
	    "Operand value too large";
    public static final String VALUE_TOO_SMALL =
	    "Operand value too small";
    public static final String JUNK_AT_END =
	    "Extra text at end of statement";

    private JTextArea statusArea;
    private JScrollPane scrollPane;

    private Map symbolTable;
    private String errorString;
    private Token errorToken;

    private boolean isValidLabel(String str) {
	for (int i = 0; i < str.length(); i++) {
	    if (!Character.isJavaIdentifierPart(str.charAt(i))) {
		return false;
	    }
	}
	return true;
    }

    private String getLabel(Statement st) {
	Token labelToken = st.getLabel();
	String label = labelToken.getText();
	// System.out.println("Label = " + label + ".");
	if (!isValidLabel(label)) {
	    errorString = INVALID_LABEL;
	    errorToken = labelToken;
	}
	if (symbolTable.get(label) != null) {
	    errorString = DUPLICATE_LABEL;
	    errorToken = labelToken;
	}
	return label;
    }

    private Instruction getInstruction(Statement st) {
	Token mnemonicToken = st.getMnemonic();
	String mnemonic = mnemonicToken.getText();
	if (mnemonic.length() == 0) {
	    return null;
	}
	Instruction instruction = Instruction.get(mnemonic);
	if (instruction == null) {
	    errorString = UNKNOWN_MNEMONIC;
	    errorToken = mnemonicToken;
	}
	return instruction;
    }

    private int getOperandValue(Statement st) {
	Token operandToken = st.getOperand();
	String operand = operandToken.getText();
	Instruction instruction = Instruction.get(st.getMnemonic().getText());
	int operandSize = instruction.getOperandSize();
	int value = 0;
	if (operand.length() == 0 && operandSize > 0) {
	    errorString = MISSING_OPERAND;
	}
	if (operand.length() > 0 && operandSize == 0) {
	    errorString = UNEXPECTED_OPERAND;
	}
	if (operand.length() == 0) {
	    if (errorString != null) {
		errorToken = operandToken;
	    }
	    return 0;
	}
	char firstCharacter = operand.charAt(0);
	if (Character.isJavaIdentifierStart(firstCharacter)) {
	    if (!isValidLabel(operand)) {
		errorString = INVALID_OPERAND;
	    }
	    Integer address = (Integer)symbolTable.get(operand);
	    if (address == null) {
		errorString = UNKNOWN_LABEL;
	    } else {
		value = address.intValue();
	    }
	} else if (Character.isDigit(firstCharacter) || firstCharacter == '-') {
	    try {
		value = Integer.parseInt(operand);
	    } catch(NumberFormatException e) {
		errorString = INVALID_OPERAND;
	    }
	    if (instruction.equals(Instruction.get("DAT"))) {
		if (value < -500) {
		    errorString = VALUE_TOO_SMALL;
		}
		if (value >= 500) {
		    errorString = VALUE_TOO_LARGE;
		}
	    } else {
		if (value < 0) {
		    errorString = ADDRESS_TOO_SMALL;
		}
		if (value >= 100) {
		    errorString = ADDRESS_TOO_LARGE;
		}
	    }
	} else {
	    errorString = INVALID_OPERAND;
	}
	if (value < 0) {
	    value += 1000;
	}
	// System.out.println("Value = " + value);
	if (errorString != null) {
	    errorToken = operandToken;
	}
	return value;
    }

    private void checkForJunk(Statement st) {
	Token junkToken = st.getJunk();
	if (junkToken.getLength() > 0) {
	    errorString = JUNK_AT_END;
	    errorToken = junkToken;
	}
    }

    private void buildSymbolTable() {
	symbolTable.clear();
	Iterator iter = LMC.getEditor().getStatements();
	int locationCounter = 0;
	while (iter.hasNext()) {
	    Statement statement = (Statement)iter.next();
	    // System.out.println("    Parsing statement: " + statement);
	    // System.out.println("LC = " + locationCounter);
	    statement.setAddress(locationCounter);
	    // System.out.println("address = " + statement.getAddress());
	    String label = getLabel(statement);
	    if (errorString != null) {
		return;
	    }
	    if (label.length() > 0) {
		symbolTable.put(label, new Integer(locationCounter));
	    }
	    Token mnemonicToken = statement.getMnemonic();
	    if (mnemonicToken.getLength() > 0) {
		locationCounter++;
	    }
	}
    }

    private void generateCode() {
	Iterator iter = LMC.getEditor().getStatements();
	int locationCounter = 0;
	while (iter.hasNext()) {
	    Statement statement = (Statement)iter.next();
	    // System.out.println("    Coding statement: " + statement);
	    Instruction instruction = getInstruction(statement);
	    if (errorString != null) {
		return;
	    }
	    if (instruction == null) {
		continue;
	    }
	    int operandValue = getOperandValue(statement);
	    if (errorString != null) {
		return;
	    }
	    checkForJunk(statement);
	    if (errorString != null) {
		return;
	    }
	    int machineCode = instruction.getOpcode() + operandValue;
	    statement.setMachineCode(machineCode);
	    int address = statement.getAddress();
	    // System.out.println("Code " + address + " " + machineCode);
	}
    }

    protected Action[] createToolBarActions() {
	Action assembleAction = new AbstractAction() {
	    public void actionPerformed(ActionEvent e) {
		assemble();
	    }
	};
	assembleAction.putValue(Action.NAME, "Assemble");
	assembleAction.putValue(Action.SHORT_DESCRIPTION,
		"Assemble the program in the editor");
	return new Action[] {
	    assembleAction
	};
    }

    protected JComponent createVisual() {
	statusArea = new JTextArea(6, 40);
	statusArea.setEditable(false);
        statusArea.setFont(LMC.font);
	scrollPane = new JScrollPane(statusArea,
		JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
		JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
	return scrollPane;
    }

    public Assembler() {
	super();
	frame.setTitle("LMC Assembler");
	Dimension eDim = LMC.getEditor().getFrame().getSize();
	frame.setLocation(eDim.width + 10, 0);
	frame.setIconifiable(true);
	frame.setResizable(true);
	symbolTable = new HashMap();
    }

    public JComponent getComponent() {
	return scrollPane;
    }

    public void clearStatus() {
	statusArea.setText("");
    }

    public void assemble() {
	clearStatus();
	javax.swing.Timer timer =
		new javax.swing.Timer(200, new ActionListener() {
	    public void actionPerformed(ActionEvent e) {
		LMC.getEditor().setEditable(false);
		LMC.getComputer().setLoadEnabled(false);
		LMC.getEditor().parseStatements();
		statusArea.append("Building symbol table.\n");
		errorString = null;
		buildSymbolTable();
		if (errorString == null) {
		    statusArea.append("Generating code.\n");
		    generateCode();
		}
		if (errorString == null) {
		    statusArea.append("Assembly successful.\n");
		    LMC.getComputer().setLoadEnabled(true);
		} else {
		    LMC.getEditor().selectToken(errorToken);
		    statusArea.append("Error during assembly:\n");
		    statusArea.append(errorString + "\n");
		}
		LMC.getEditor().setEditable(true);
	    }
	});
	timer.setInitialDelay(200);
	timer.setRepeats(false);
	timer.setCoalesce(false);
	timer.restart();
    }

}

